Session lead: Katie Smith (Centre for Ecology & Hydrology) k.a.smith@ceh.ac.uk

This is an demonstration of how to use the airGR package of hydrological models in R, as well as how to plot interactive timeseries graphs with the dygraphs package.

First we need to install some packages

library(xts)
package <U+393C><U+3E31>xts<U+393C><U+3E32> was built under R version 3.3.3Loading required package: zoo

Attaching package: <U+393C><U+3E31>zoo<U+393C><U+3E32>

The following objects are masked from <U+393C><U+3E31>package:base<U+393C><U+3E32>:

    as.Date, as.Date.numeric
library(dygraphs)
package <U+393C><U+3E31>dygraphs<U+393C><U+3E32> was built under R version 3.3.3

Now we’ll load in some observational flow data from the River Thames (naturalised) in England - with thanks to the National River Flow Archive: http://nrfa.ceh.ac.uk/data/search

observed_data <- read.csv("~/Easter_Work/GR_teaching/Qobs_390010.csv")
observed_data
observed_data$DATE <- strptime(observed_data$DATE, format = "%d/%m/%Y")

in order to plot this as an interactive dygraph we need to change it to xts format

observed_data_xts <- as.xts(observed_data$Qobs, order.by = observed_data$DATE)
dygraph(observed_data_xts)%>%
  dyOptions()%>%
  dyRangeSelector()

Now lets read in some precipitation data - this is from CEH-GEAR: https://data.gov.uk/dataset/gridded-estimates-of-daily-and-monthly-areal-rainfall-for-the-united-kingdom-1890-2012-ceh-gear

precip_data <- read.csv("~/Easter_Work/GR_Teaching/rain_1961_2014_390010.csv")
precip_data

and some potential evapotranspiration data - this is from CHESS-PE:https://data.gov.uk/dataset/climate-hydrology-and-ecology-research-support-system-potential-evapotranspiration-dataset-for-1

PET_data <- read.csv("~/Easter_Work/GR_Teaching/CHESS_PET_1961_2015_390010.csv")
PET_data

Note that our starting dates are not the same as our observational data, so we need to make a dataframe that matches the dates up. There are a lot of ways to do this. First we’ll convert them to the same date format.

precip_data$DATE <- strptime(precip_data$DATE, "%Y-%m-%d")
PET_data$DATE <- strptime(PET_data$DATE, "%Y-%m-%d")

now we’ll find the common period

first_date <- max(observed_data$DATE[1], precip_data$DATE[1], PET_data$DATE[1])
last_date <- min(observed_data$DATE[length(observed_data$DATE)], precip_data$DATE[length(precip_data$DATE)], PET_data$DATE[length(PET_data$DATE)])

and make a data frame of that length

# make an empty data frame
thames_data <- as.data.frame(matrix(NA,nrow=as.numeric((last_date-first_date)+1), ncol=4))
colnames(thames_data) <-c ("date","PET","precip","obs")
# make the date timeseries
thames_data$date <- seq(first_date, last_date, by="days")
# populate the data frame with the data
thames_data$obs <- observed_data$Qobs[which(observed_data$DATE==thames_data$date[1]):which(observed_data$DATE==thames_data$date[length(thames_data$date)])]
thames_data$precip <- precip_data$Mean_rainfall[which(precip_data$DATE==thames_data$date[1]):which(precip_data$DATE==thames_data$date[length(thames_data$date)])]
thames_data$PET <- PET_data$PET[which(PET_data$DATE==thames_data$date[1]):which(PET_data$DATE==thames_data$date[length(thames_data$date)])]

plot the observed streamflow with the precipitation data

#  convert the observed discharge to runoff (so its in the same units as the precip)
# divide by catchment area (m2) and mulitply by 86.4
thames_data$obs <- (thames_data$obs/9948.0)*86.4
thames_data_xts <- as.xts(thames_data[,3:4], order.by=thames_data$date)
# initiate the dygraph
dygraph(thames_data_xts, main = "Naturalised Runoff and Precipitation Observations for the Thames at Kingston")%>%
# define the first axis  
dyAxis(dygraph = graphOut, name = "y", label = "runoff (mm/day)",
       valueRange = range(thames_data_xts[, "obs"],
                          na.rm = TRUE)* c(0.01, 1.59))%>%
# define the second axis
dyAxis(dygraph = graphOut, name = "y2", label = "precip (mm/day)",
                   valueRange = rev(range(thames_data_xts[, "precip"], 
                   na.rm = TRUE)* c(0.01, 2.99)))%>%
# plot the data
dySeries("obs",axis = 'y')%>%
dySeries("precip", axis = 'y2', stepPlot = TRUE,
         fillGraph = TRUE)%>%
dyOptions(colors = RColorBrewer::brewer.pal(3,"Set1")[3:1]) %>%
dyRangeSelector()

OK, enough messing with data, lets do some modelling

see this website for a good guide through the model: https://odelaigue.github.io/airGR/tutorial_1_getting_started.html

import the GR package

require(airGR, quietly=TRUE)
package <U+393C><U+3E31>airGR<U+393C><U+3E32> was built under R version 3.3.3

prepare the input data in the correct format

BasinObs <- thames_data
colnames(BasinObs) <- c('DatesR','E','P', 'Qobs')

create the InputsModel object - this defines which model we want to run, and defines the variables for the models input data

InputsModel <- CreateInputsModel(FUN_MOD = RunModel_GR4J,DatesR = BasinObs$DatesR,
                                   Precip = BasinObs$P,PotEvap = BasinObs$E)
str(InputsModel)
List of 3
 $ DatesR : POSIXlt[1:19723], format: "1961-01-01 00:00:00" "1961-01-02 00:00:00" ...
 $ Precip : num [1:19723] 8.6376 4.571 2.1015 0.0201 7.1859 ...
 $ PotEvap: num [1:19723] 0.367 0.353 0.412 0.32 0.612 ...
 - attr(*, "class")= chr [1:3] "InputsModel" "daily" "GR"
# note NA values of precip and PET are NOT ALLOWED

create the RunOptions object - this defines options for the RunModel_GR4J function

# first define the period to run the model over
Ind_Run <- seq(which(BasinObs$DatesR=="1981-01-01"),
             which(BasinObs$DatesR=="2014-12-31"))
RunOptions <- CreateRunOptions(FUN_MOD = RunModel_GR4J,
                               InputsModel = InputsModel,
                               IndPeriod_Run = Ind_Run,
                               IndPeriod_WarmUp = NULL)
     Model warm-up period not defined -> default configuration used 
        The year preceding the run period is used 
     Model states initialisation not defined -> default configuration used 
str(RunOptions)
List of 6
 $ IndPeriod_WarmUp: int [1:365] 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 ...
 $ IndPeriod_Run   : int [1:12418] 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315 ...
 $ IniStates       : num [1:67] 0 0 0 0 0 0 0 0 0 0 ...
 $ IniResLevels    : num [1:2] 0.3 0.5
 $ Outputs_Cal     : chr "Qsim"
 $ Outputs_Sim     : chr [1:16] "DatesR" "PotEvap" "Precip" "Prod" ...
 - attr(*, "class")= chr [1:3] "RunOptions" "GR" "daily"

create the InputsCrit object - define the error metric (choose from RMSE, NSE, KGE or modified KGE (KGE2))

InputsCrit <- CreateInputsCrit(FUN_CRIT = ErrorCrit_NSE,
                               InputsModel = InputsModel,
                               RunOptions = RunOptions,
                               Qobs = BasinObs$Qobs[Ind_Run])
str(InputsCrit)
List of 5
 $ BoolCrit  : logi [1:12418] TRUE TRUE TRUE TRUE TRUE TRUE ...
 $ Qobs      : num [1:12418] 0.635 0.623 0.587 0.571 0.566 ...
 $ transfo   : chr ""
 $ Ind_zeroes: NULL
 $ epsilon   : NULL
 - attr(*, "class")= chr "InputsCrit"

create the CalibOptions object - choose the calibration algorithm

CalibOptions <- CreateCalibOptions(FUN_MOD = RunModel_GR4J,
                                   FUN_CALIB = Calibration_Michel)
str(CalibOptions)
List of 3
 $ FixedParam       : logi [1:4] NA NA NA NA
 $ SearchRanges     : num [1:2, 1:4] 4.59e-05 2.18e+04 -1.09e+04 1.09e+04 4.59e-05 ...
 $ StartParamDistrib: num [1:3, 1:4] 169.017 247.151 432.681 -2.376 -0.649 ...
 - attr(*, "class")= chr [1:3] "CalibOptions" "GR4J" "HBAN"

run the calibration

OutputsCalib <- Calibration_Michel(InputsModel = InputsModel, 
                                   RunOptions = RunOptions,
                                   InputsCrit = InputsCrit,
                                   CalibOptions = CalibOptions,
                                   FUN_MOD = RunModel_GR4J,
                                   FUN_CRIT = ErrorCrit_NSE)
Grid-Screening in progress (0% 20% 40% 60% 80% 100%)
     Screening completed (81 runs)
         Param =  432.681 ,   -0.649 ,   83.096 ,    2.384
         Crit NSE[Q]       = 0.8923
Steepest-descent local search in progress
     Calibration completed (19 iterations, 216 runs)
         Param =  607.894 ,   -0.734 ,   87.357 ,    2.315
         Crit NSE[Q]       = 0.9246

NSE of 0.9246 - not bad at all!

define the parameters found by the calibration routine

Param <- OutputsCalib$ParamFinalR
Param
[1] 607.8936811  -0.7336304  87.3567230   2.3153153

RUN THE MODEL!

OutputsModel <- RunModel_GR4J(InputsModel = InputsModel,
                             RunOptions = RunOptions,
                             Param= Param)
str(OutputsModel)
List of 16
 $ DatesR  : POSIXlt[1:12418], format: "1981-01-01 00:00:00" "1981-01-02 00:00:00" ...
 $ PotEvap : num [1:12418] 1.347 0.38 0.795 0.57 0.454 ...
 $ Precip  : num [1:12418] 0.0972 0.1211 0.0523 0.1147 0.3248 ...
 $ Prod    : num [1:12418] 352 351 350 350 349 ...
 $ AE      : num [1:12418] 1.127 0.334 0.663 0.488 0.43 ...
 $ Perc    : num [1:12418] 0.387 0.383 0.378 0.374 0.372 ...
 $ PR      : num [1:12418] 0.387 0.383 0.378 0.374 0.372 ...
 $ Q9      : num [1:12418] 0.355 0.35 0.345 0.341 0.338 ...
 $ Q1      : num [1:12418] 0.0398 0.0393 0.0387 0.0382 0.0378 ...
 $ Rout    : num [1:12418] 42.2 42 41.7 41.4 41.2 ...
 $ Exch    : num [1:12418] -0.0592 -0.0577 -0.0564 -0.0551 -0.0539 ...
 $ AExch   : num [1:12418] -0.099 -0.097 -0.0951 -0.0933 -0.0917 ...
 $ QR      : num [1:12418] 0.599 0.578 0.559 0.542 0.526 ...
 $ QD      : num [1:12418] 0 0 0 0 0 ...
 $ Qsim    : num [1:12418] 0.599 0.578 0.559 0.542 0.526 ...
 $ StateEnd: num [1:67] 374.4 45.5 NA NA NA ...
 - attr(*, "class")= chr [1:3] "OutputsModel" "daily" "GR"

use the inbuilt plot function to look at the results

plot(OutputsModel, Qobs=BasinObs$Qobs[Ind_Run])

looking good - but we’ve got some discrepancy at the low flows end. NSE is notorious for this, it is based on the square of the flows, so over-weights the calibration to the high flows. I wonder if the modified KGE can do any better?

# make a few changes to the calibration criteria
InputsCrit <- CreateInputsCrit(FUN_CRIT = ErrorCrit_KGE2,
                               InputsModel = InputsModel,
                               RunOptions = RunOptions,
                               Qobs = BasinObs$Qobs[Ind_Run])
# rerun the calibration
OutputsCalib <- Calibration_Michel(InputsModel = InputsModel, 
                                   RunOptions = RunOptions,
                                   InputsCrit = InputsCrit,
                                   CalibOptions = CalibOptions,
                                   FUN_MOD = RunModel_GR4J,
                                   FUN_CRIT = ErrorCrit_KGE2)
Grid-Screening in progress (0% 20% 40% 60% 80% 100%)
     Screening completed (81 runs)
         Param =  432.681 ,   -0.649 ,   83.096 ,    2.384
         Crit KGE'[Q]      = 0.8589
Steepest-descent local search in progress
     Calibration completed (51 iterations, 502 runs)
         Param =  598.748 ,   -0.690 ,   93.679 ,    2.297
         Crit KGE'[Q]      = 0.9621
# redefine the parameters
Param <- OutputsCalib$ParamFinalR
# rerun the model
OutputsModel <- RunModel_GR4J(InputsModel = InputsModel,
                             RunOptions = RunOptions,
                             Param= Param)
# plot again
plot(OutputsModel, Qobs=BasinObs$Qobs[Ind_Run])

not much different. Oh well, we can be happy with either of those metric scores. - pause for thought - which parameter set would you choose to use?!

Let’s do some validation

# go back to the beginning, redefine the period to run on (the period we haven't used for calibration, minus the first year needed for warm up)
Ind_Run <- seq(which(BasinObs$DatesR=="1962-01-01"),
             which(BasinObs$DatesR=="1980-12-31"))
RunOptions <- CreateRunOptions(FUN_MOD = RunModel_GR4J,
                               InputsModel = InputsModel,
                               IndPeriod_Run = Ind_Run,
                               IndPeriod_WarmUp = NULL)
     Model warm-up period not defined -> default configuration used 
        The year preceding the run period is used 
     Model states initialisation not defined -> default configuration used 
InputsCrit <- CreateInputsCrit(FUN_CRIT = ErrorCrit_KGE2,
                               InputsModel = InputsModel,
                               RunOptions = RunOptions,
                               Qobs = BasinObs$Qobs[Ind_Run])
Param <- OutputsCalib$ParamFinalR
OutputsModel <- RunModel_GR4J(InputsModel = InputsModel,
                             RunOptions = RunOptions,
                             Param= Param)
OutputsCrit <- ErrorCrit_KGE2(InputsCrit = InputsCrit, 
                              OutputsModel = OutputsModel)
Crit. KGE'[Q] = 0.9039
    SubCrit. KGE'[Q] cor(sim, obs, "pearson") = 0.9466 
    SubCrit. KGE'[Q] sd(sim)/sd(obs)          = 0.9253 
    SubCrit. KGE'[Q] mean(sim)/mean(obs)      = 1.0282 

slightly worse than the calibration period (0.9621) but not bad at all

finally, lets run the model for the whole time period and plot a dygraph so we can look at the timeseries in more detail

Ind_Run <- seq(which(BasinObs$DatesR=="1962-01-01"),
             which(BasinObs$DatesR=="2014-12-31"))
RunOptions <- CreateRunOptions(FUN_MOD = RunModel_GR4J,
                               InputsModel = InputsModel,
                               IndPeriod_Run = Ind_Run,
                               IndPeriod_WarmUp = NULL)
     Model warm-up period not defined -> default configuration used 
        The year preceding the run period is used 
     Model states initialisation not defined -> default configuration used 
Param <- OutputsCalib$ParamFinalR
OutputsModel <- RunModel_GR4J(InputsModel = InputsModel,
                             RunOptions = RunOptions,
                             Param= Param)
plot_output_data <- as.data.frame(matrix(NA, ncol = 3,
                                         nrow = length(OutputsModel$DatesR)))
colnames(plot_output_data) <- c("Date", "Qsim", "Qobs")
plot_output_data$Date <- OutputsModel$DatesR
plot_output_data$Qsim <- OutputsModel$Qsim
plot_output_data$Qobs <- BasinObs$Qobs[Ind_Run]
plot_output_data_xts <- as.xts(plot_output_data, order.by = plot_output_data$Date)
library(RColorBrewer)
dygraph(plot_output_data_xts, main="Observed and Simulated Runoff for the Thames at Kingston (Naturalised)")%>%
  dyOptions(colors = RColorBrewer::brewer.pal(3,"Set1")[3:1])%>%
  dyAxis("y", label="Runoff (mm/day)")%>%
  dyRangeSelector()
LS0tDQp0aXRsZTogIkh5ZHJvbG9neSBpbiBSIGRlbW9uc3RyYXRpb24gYWlyR1IgLSBFR1UgMjQvMDQvMjAxNyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQpTZXNzaW9uIGxlYWQ6IEthdGllIFNtaXRoIChDZW50cmUgZm9yIEVjb2xvZ3kgJiBIeWRyb2xvZ3kpIGsuYS5zbWl0aEBjZWguYWMudWsNCg0KVGhpcyBpcyBhbiBkZW1vbnN0cmF0aW9uIG9mIGhvdyB0byB1c2UgdGhlIGFpckdSIHBhY2thZ2Ugb2YgaHlkcm9sb2dpY2FsIG1vZGVscyBpbiBSLCBhcyB3ZWxsIGFzIGhvdyB0byBwbG90IGludGVyYWN0aXZlIHRpbWVzZXJpZXMgZ3JhcGhzIHdpdGggdGhlIGR5Z3JhcGhzIHBhY2thZ2UuDQoNCiMjIyMgRmlyc3Qgd2UgbmVlZCB0byBpbnN0YWxsIHNvbWUgcGFja2FnZXMNCmBgYHtyfQ0KbGlicmFyeSh4dHMpDQpsaWJyYXJ5KGR5Z3JhcGhzKQ0KYGBgDQoNCiMjIyMgTm93IHdlJ2xsIGxvYWQgaW4gc29tZSBvYnNlcnZhdGlvbmFsIGZsb3cgZGF0YSBmcm9tIHRoZSBSaXZlciBUaGFtZXMgKG5hdHVyYWxpc2VkKSBpbiBFbmdsYW5kIC0gd2l0aCB0aGFua3MgdG8gdGhlIE5hdGlvbmFsIFJpdmVyIEZsb3cgQXJjaGl2ZTogaHR0cDovL25yZmEuY2VoLmFjLnVrL2RhdGEvc2VhcmNoDQpgYGB7cn0NCm9ic2VydmVkX2RhdGEgPC0gcmVhZC5jc3YoIn4vRWFzdGVyX1dvcmsvR1JfdGVhY2hpbmcvUW9ic18zOTAwMTAuY3N2IikNCm9ic2VydmVkX2RhdGENCm9ic2VydmVkX2RhdGEkREFURSA8LSBzdHJwdGltZShvYnNlcnZlZF9kYXRhJERBVEUsIGZvcm1hdCA9ICIlZC8lbS8lWSIpDQpgYGANCg0KIyMjIyBpbiBvcmRlciB0byBwbG90IHRoaXMgYXMgYW4gaW50ZXJhY3RpdmUgZHlncmFwaCB3ZSBuZWVkIHRvIGNoYW5nZSBpdCB0byB4dHMgZm9ybWF0DQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9DQpvYnNlcnZlZF9kYXRhX3h0cyA8LSBhcy54dHMob2JzZXJ2ZWRfZGF0YSRRb2JzLCBvcmRlci5ieSA9IG9ic2VydmVkX2RhdGEkREFURSkNCmR5Z3JhcGgob2JzZXJ2ZWRfZGF0YV94dHMpJT4lDQogIGR5T3B0aW9ucygpJT4lDQogIGR5UmFuZ2VTZWxlY3RvcigpDQpgYGANCiMjIyMgTm93IGxldHMgcmVhZCBpbiBzb21lIHByZWNpcGl0YXRpb24gZGF0YSAtIHRoaXMgaXMgZnJvbSBDRUgtR0VBUjogaHR0cHM6Ly9kYXRhLmdvdi51ay9kYXRhc2V0L2dyaWRkZWQtZXN0aW1hdGVzLW9mLWRhaWx5LWFuZC1tb250aGx5LWFyZWFsLXJhaW5mYWxsLWZvci10aGUtdW5pdGVkLWtpbmdkb20tMTg5MC0yMDEyLWNlaC1nZWFyDQpgYGB7cn0NCnByZWNpcF9kYXRhIDwtIHJlYWQuY3N2KCJ+L0Vhc3Rlcl9Xb3JrL0dSX1RlYWNoaW5nL3JhaW5fMTk2MV8yMDE0XzM5MDAxMC5jc3YiKQ0KcHJlY2lwX2RhdGENCmBgYA0KIyMjIyBhbmQgc29tZSBwb3RlbnRpYWwgZXZhcG90cmFuc3BpcmF0aW9uIGRhdGEgLSB0aGlzIGlzIGZyb20gQ0hFU1MtUEU6aHR0cHM6Ly9kYXRhLmdvdi51ay9kYXRhc2V0L2NsaW1hdGUtaHlkcm9sb2d5LWFuZC1lY29sb2d5LXJlc2VhcmNoLXN1cHBvcnQtc3lzdGVtLXBvdGVudGlhbC1ldmFwb3RyYW5zcGlyYXRpb24tZGF0YXNldC1mb3ItMQ0KDQpgYGB7cn0NClBFVF9kYXRhIDwtIHJlYWQuY3N2KCJ+L0Vhc3Rlcl9Xb3JrL0dSX1RlYWNoaW5nL0NIRVNTX1BFVF8xOTYxXzIwMTVfMzkwMDEwLmNzdiIpDQpQRVRfZGF0YQ0KYGBgDQoNCiMjIyMgTm90ZSB0aGF0IG91ciBzdGFydGluZyBkYXRlcyBhcmUgbm90IHRoZSBzYW1lIGFzIG91ciBvYnNlcnZhdGlvbmFsIGRhdGEsIHNvIHdlIG5lZWQgdG8gbWFrZSBhIGRhdGFmcmFtZSB0aGF0IG1hdGNoZXMgdGhlIGRhdGVzIHVwLiBUaGVyZSBhcmUgYSBsb3Qgb2Ygd2F5cyB0byBkbyB0aGlzLiBGaXJzdCB3ZSdsbCBjb252ZXJ0IHRoZW0gdG8gdGhlIHNhbWUgZGF0ZSBmb3JtYXQuDQpgYGB7cn0NCnByZWNpcF9kYXRhJERBVEUgPC0gc3RycHRpbWUocHJlY2lwX2RhdGEkREFURSwgIiVZLSVtLSVkIikNClBFVF9kYXRhJERBVEUgPC0gc3RycHRpbWUoUEVUX2RhdGEkREFURSwgIiVZLSVtLSVkIikNCmBgYA0KIyMjIyBub3cgd2UnbGwgZmluZCB0aGUgY29tbW9uIHBlcmlvZA0KYGBge3J9DQpmaXJzdF9kYXRlIDwtIG1heChvYnNlcnZlZF9kYXRhJERBVEVbMV0sIHByZWNpcF9kYXRhJERBVEVbMV0sIFBFVF9kYXRhJERBVEVbMV0pDQpsYXN0X2RhdGUgPC0gbWluKG9ic2VydmVkX2RhdGEkREFURVtsZW5ndGgob2JzZXJ2ZWRfZGF0YSREQVRFKV0sIHByZWNpcF9kYXRhJERBVEVbbGVuZ3RoKHByZWNpcF9kYXRhJERBVEUpXSwgUEVUX2RhdGEkREFURVtsZW5ndGgoUEVUX2RhdGEkREFURSldKQ0KYGBgDQojIyMjIGFuZCBtYWtlIGEgZGF0YSBmcmFtZSBvZiB0aGF0IGxlbmd0aA0KYGBge3J9DQojIG1ha2UgYW4gZW1wdHkgZGF0YSBmcmFtZQ0KdGhhbWVzX2RhdGEgPC0gYXMuZGF0YS5mcmFtZShtYXRyaXgoTkEsbnJvdz1hcy5udW1lcmljKChsYXN0X2RhdGUtZmlyc3RfZGF0ZSkrMSksIG5jb2w9NCkpDQpjb2xuYW1lcyh0aGFtZXNfZGF0YSkgPC1jICgiZGF0ZSIsIlBFVCIsInByZWNpcCIsIm9icyIpDQojIG1ha2UgdGhlIGRhdGUgdGltZXNlcmllcw0KdGhhbWVzX2RhdGEkZGF0ZSA8LSBzZXEoZmlyc3RfZGF0ZSwgbGFzdF9kYXRlLCBieT0iZGF5cyIpDQojIHBvcHVsYXRlIHRoZSBkYXRhIGZyYW1lIHdpdGggdGhlIGRhdGENCnRoYW1lc19kYXRhJG9icyA8LSBvYnNlcnZlZF9kYXRhJFFvYnNbd2hpY2gob2JzZXJ2ZWRfZGF0YSREQVRFPT10aGFtZXNfZGF0YSRkYXRlWzFdKTp3aGljaChvYnNlcnZlZF9kYXRhJERBVEU9PXRoYW1lc19kYXRhJGRhdGVbbGVuZ3RoKHRoYW1lc19kYXRhJGRhdGUpXSldDQp0aGFtZXNfZGF0YSRwcmVjaXAgPC0gcHJlY2lwX2RhdGEkTWVhbl9yYWluZmFsbFt3aGljaChwcmVjaXBfZGF0YSREQVRFPT10aGFtZXNfZGF0YSRkYXRlWzFdKTp3aGljaChwcmVjaXBfZGF0YSREQVRFPT10aGFtZXNfZGF0YSRkYXRlW2xlbmd0aCh0aGFtZXNfZGF0YSRkYXRlKV0pXQ0KdGhhbWVzX2RhdGEkUEVUIDwtIFBFVF9kYXRhJFBFVFt3aGljaChQRVRfZGF0YSREQVRFPT10aGFtZXNfZGF0YSRkYXRlWzFdKTp3aGljaChQRVRfZGF0YSREQVRFPT10aGFtZXNfZGF0YSRkYXRlW2xlbmd0aCh0aGFtZXNfZGF0YSRkYXRlKV0pXQ0KYGBgDQojIyMjIHBsb3QgdGhlIG9ic2VydmVkIHN0cmVhbWZsb3cgd2l0aCB0aGUgcHJlY2lwaXRhdGlvbiBkYXRhDQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9DQojICBjb252ZXJ0IHRoZSBvYnNlcnZlZCBkaXNjaGFyZ2UgdG8gcnVub2ZmIChzbyBpdHMgaW4gdGhlIHNhbWUgdW5pdHMgYXMgdGhlIHByZWNpcCkNCiMgZGl2aWRlIGJ5IGNhdGNobWVudCBhcmVhIChtMikgYW5kIG11bGl0cGx5IGJ5IDg2LjQNCnRoYW1lc19kYXRhJG9icyA8LSAodGhhbWVzX2RhdGEkb2JzLzk5NDguMCkqODYuNA0KdGhhbWVzX2RhdGFfeHRzIDwtIGFzLnh0cyh0aGFtZXNfZGF0YVssMzo0XSwgb3JkZXIuYnk9dGhhbWVzX2RhdGEkZGF0ZSkNCiMgaW5pdGlhdGUgdGhlIGR5Z3JhcGgNCmR5Z3JhcGgodGhhbWVzX2RhdGFfeHRzLCBtYWluID0gIk5hdHVyYWxpc2VkIFJ1bm9mZiBhbmQgUHJlY2lwaXRhdGlvbiBPYnNlcnZhdGlvbnMgZm9yIHRoZSBUaGFtZXMgYXQgS2luZ3N0b24iKSU+JQ0KIyBkZWZpbmUgdGhlIGZpcnN0IGF4aXMgIA0KZHlBeGlzKGR5Z3JhcGggPSBncmFwaE91dCwgbmFtZSA9ICJ5IiwgbGFiZWwgPSAicnVub2ZmIChtbS9kYXkpIiwNCiAgICAgICB2YWx1ZVJhbmdlID0gcmFuZ2UodGhhbWVzX2RhdGFfeHRzWywgIm9icyJdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybSA9IFRSVUUpKiBjKDAuMDEsIDEuNTkpKSU+JQ0KIyBkZWZpbmUgdGhlIHNlY29uZCBheGlzDQpkeUF4aXMoZHlncmFwaCA9IGdyYXBoT3V0LCBuYW1lID0gInkyIiwgbGFiZWwgPSAicHJlY2lwIChtbS9kYXkpIiwNCiAgICAgICAgICAgICAgICAgICB2YWx1ZVJhbmdlID0gcmV2KHJhbmdlKHRoYW1lc19kYXRhX3h0c1ssICJwcmVjaXAiXSwgDQogICAgICAgICAgICAgICAgICAgbmEucm0gPSBUUlVFKSogYygwLjAxLCAyLjk5KSkpJT4lDQojIHBsb3QgdGhlIGRhdGENCmR5U2VyaWVzKCJvYnMiLGF4aXMgPSAneScpJT4lDQpkeVNlcmllcygicHJlY2lwIiwgYXhpcyA9ICd5MicsIHN0ZXBQbG90ID0gVFJVRSwNCiAgICAgICAgIGZpbGxHcmFwaCA9IFRSVUUpJT4lDQpkeU9wdGlvbnMoY29sb3JzID0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDMsIlNldDEiKVszOjFdKSAlPiUNCmR5UmFuZ2VTZWxlY3RvcigpDQpgYGANCg0KIyMjIE9LLCBlbm91Z2ggbWVzc2luZyB3aXRoIGRhdGEsIGxldHMgZG8gc29tZSBtb2RlbGxpbmcNCg0KIyMjIyBzZWUgdGhpcyB3ZWJzaXRlIGZvciBhIGdvb2QgZ3VpZGUgdGhyb3VnaCB0aGUgbW9kZWw6IGh0dHBzOi8vb2RlbGFpZ3VlLmdpdGh1Yi5pby9haXJHUi90dXRvcmlhbF8xX2dldHRpbmdfc3RhcnRlZC5odG1sDQoNCiMjIyMgaW1wb3J0IHRoZSBHUiBwYWNrYWdlDQpgYGB7cn0NCnJlcXVpcmUoYWlyR1IsIHF1aWV0bHk9VFJVRSkNCmBgYA0KIyMjIyBwcmVwYXJlIHRoZSBpbnB1dCBkYXRhIGluIHRoZSBjb3JyZWN0IGZvcm1hdA0KYGBge3J9DQpCYXNpbk9icyA8LSB0aGFtZXNfZGF0YQ0KY29sbmFtZXMoQmFzaW5PYnMpIDwtIGMoJ0RhdGVzUicsJ0UnLCdQJywgJ1FvYnMnKQ0KYGBgDQojIyMjIGNyZWF0ZSB0aGUgSW5wdXRzTW9kZWwgb2JqZWN0IC0gdGhpcyBkZWZpbmVzIHdoaWNoIG1vZGVsIHdlIHdhbnQgdG8gcnVuLCBhbmQgZGVmaW5lcyB0aGUgdmFyaWFibGVzIGZvciB0aGUgbW9kZWxzIGlucHV0IGRhdGENCg0KYGBge3J9DQpJbnB1dHNNb2RlbCA8LSBDcmVhdGVJbnB1dHNNb2RlbChGVU5fTU9EID0gUnVuTW9kZWxfR1I0SixEYXRlc1IgPSBCYXNpbk9icyREYXRlc1IsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpcCA9IEJhc2luT2JzJFAsUG90RXZhcCA9IEJhc2luT2JzJEUpDQpzdHIoSW5wdXRzTW9kZWwpDQojIG5vdGUgTkEgdmFsdWVzIG9mIHByZWNpcCBhbmQgUEVUIGFyZSBOT1QgQUxMT1dFRA0KYGBgDQojIyMjIGNyZWF0ZSB0aGUgUnVuT3B0aW9ucyBvYmplY3QgLSB0aGlzIGRlZmluZXMgb3B0aW9ucyBmb3IgdGhlIFJ1bk1vZGVsX0dSNEogZnVuY3Rpb24NCmBgYHtyfQ0KIyBmaXJzdCBkZWZpbmUgdGhlIHBlcmlvZCB0byBydW4gdGhlIG1vZGVsIG92ZXINCkluZF9SdW4gPC0gc2VxKHdoaWNoKEJhc2luT2JzJERhdGVzUj09IjE5ODEtMDEtMDEiKSwNCiAgICAgICAgICAgICB3aGljaChCYXNpbk9icyREYXRlc1I9PSIyMDE0LTEyLTMxIikpDQpSdW5PcHRpb25zIDwtIENyZWF0ZVJ1bk9wdGlvbnMoRlVOX01PRCA9IFJ1bk1vZGVsX0dSNEosDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5wdXRzTW9kZWwgPSBJbnB1dHNNb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbmRQZXJpb2RfUnVuID0gSW5kX1J1biwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbmRQZXJpb2RfV2FybVVwID0gTlVMTCkNCnN0cihSdW5PcHRpb25zKQ0KYGBgDQojIyMjIGNyZWF0ZSB0aGUgSW5wdXRzQ3JpdCBvYmplY3QgLSBkZWZpbmUgdGhlIGVycm9yIG1ldHJpYyAoY2hvb3NlIGZyb20gUk1TRSwgTlNFLCBLR0Ugb3IgbW9kaWZpZWQgS0dFIChLR0UyKSkNCmBgYHtyfQ0KSW5wdXRzQ3JpdCA8LSBDcmVhdGVJbnB1dHNDcml0KEZVTl9DUklUID0gRXJyb3JDcml0X05TRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbnB1dHNNb2RlbCA9IElucHV0c01vZGVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJ1bk9wdGlvbnMgPSBSdW5PcHRpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFFvYnMgPSBCYXNpbk9icyRRb2JzW0luZF9SdW5dKQ0Kc3RyKElucHV0c0NyaXQpDQpgYGANCiMjIyMgY3JlYXRlIHRoZSBDYWxpYk9wdGlvbnMgb2JqZWN0IC0gY2hvb3NlIHRoZSBjYWxpYnJhdGlvbiBhbGdvcml0aG0NCmBgYHtyfQ0KQ2FsaWJPcHRpb25zIDwtIENyZWF0ZUNhbGliT3B0aW9ucyhGVU5fTU9EID0gUnVuTW9kZWxfR1I0SiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOX0NBTElCID0gQ2FsaWJyYXRpb25fTWljaGVsKQ0Kc3RyKENhbGliT3B0aW9ucykNCmBgYA0KIyMjIyBydW4gdGhlIGNhbGlicmF0aW9uDQpgYGB7cn0NCk91dHB1dHNDYWxpYiA8LSBDYWxpYnJhdGlvbl9NaWNoZWwoSW5wdXRzTW9kZWwgPSBJbnB1dHNNb2RlbCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJ1bk9wdGlvbnMgPSBSdW5PcHRpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbnB1dHNDcml0ID0gSW5wdXRzQ3JpdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2FsaWJPcHRpb25zID0gQ2FsaWJPcHRpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU5fTU9EID0gUnVuTW9kZWxfR1I0SiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOX0NSSVQgPSBFcnJvckNyaXRfTlNFKQ0KYGBgDQojIyMjIE5TRSBvZiAwLjkyNDYgLSBub3QgYmFkIGF0IGFsbCENCg0KIyMjIyBkZWZpbmUgdGhlIHBhcmFtZXRlcnMgZm91bmQgYnkgdGhlIGNhbGlicmF0aW9uIHJvdXRpbmUNCmBgYHtyfQ0KUGFyYW0gPC0gT3V0cHV0c0NhbGliJFBhcmFtRmluYWxSDQpQYXJhbQ0KYGBgDQojIyMgUlVOIFRIRSBNT0RFTCENCmBgYHtyfQ0KT3V0cHV0c01vZGVsIDwtIFJ1bk1vZGVsX0dSNEooSW5wdXRzTW9kZWwgPSBJbnB1dHNNb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUnVuT3B0aW9ucyA9IFJ1bk9wdGlvbnMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBhcmFtPSBQYXJhbSkNCnN0cihPdXRwdXRzTW9kZWwpDQpgYGANCiMjIyMgdXNlIHRoZSBpbmJ1aWx0IHBsb3QgZnVuY3Rpb24gdG8gbG9vayBhdCB0aGUgcmVzdWx0cw0KYGBge3J9DQpwbG90KE91dHB1dHNNb2RlbCwgUW9icz1CYXNpbk9icyRRb2JzW0luZF9SdW5dKQ0KYGBgDQojIyMjIGxvb2tpbmcgZ29vZCAtIGJ1dCB3ZSd2ZSBnb3Qgc29tZSBkaXNjcmVwYW5jeSBhdCB0aGUgbG93IGZsb3dzIGVuZC4gTlNFIGlzIG5vdG9yaW91cyBmb3IgdGhpcywgaXQgaXMgYmFzZWQgb24gdGhlIHNxdWFyZSBvZiB0aGUgZmxvd3MsIHNvIG92ZXItd2VpZ2h0cyB0aGUgY2FsaWJyYXRpb24gdG8gdGhlIGhpZ2ggZmxvd3MuIEkgd29uZGVyIGlmIHRoZSBtb2RpZmllZCBLR0UgY2FuIGRvIGFueSBiZXR0ZXI/DQpgYGB7cn0NCiMgbWFrZSBhIGZldyBjaGFuZ2VzIHRvIHRoZSBjYWxpYnJhdGlvbiBjcml0ZXJpYQ0KSW5wdXRzQ3JpdCA8LSBDcmVhdGVJbnB1dHNDcml0KEZVTl9DUklUID0gRXJyb3JDcml0X0tHRTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5wdXRzTW9kZWwgPSBJbnB1dHNNb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSdW5PcHRpb25zID0gUnVuT3B0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBRb2JzID0gQmFzaW5PYnMkUW9ic1tJbmRfUnVuXSkNCiMgcmVydW4gdGhlIGNhbGlicmF0aW9uDQpPdXRwdXRzQ2FsaWIgPC0gQ2FsaWJyYXRpb25fTWljaGVsKElucHV0c01vZGVsID0gSW5wdXRzTW9kZWwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSdW5PcHRpb25zID0gUnVuT3B0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5wdXRzQ3JpdCA9IElucHV0c0NyaXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENhbGliT3B0aW9ucyA9IENhbGliT3B0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOX01PRCA9IFJ1bk1vZGVsX0dSNEosDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTl9DUklUID0gRXJyb3JDcml0X0tHRTIpDQojIHJlZGVmaW5lIHRoZSBwYXJhbWV0ZXJzDQpQYXJhbSA8LSBPdXRwdXRzQ2FsaWIkUGFyYW1GaW5hbFINCiMgcmVydW4gdGhlIG1vZGVsDQpPdXRwdXRzTW9kZWwgPC0gUnVuTW9kZWxfR1I0SihJbnB1dHNNb2RlbCA9IElucHV0c01vZGVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSdW5PcHRpb25zID0gUnVuT3B0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUGFyYW09IFBhcmFtKQ0KIyBwbG90IGFnYWluDQpwbG90KE91dHB1dHNNb2RlbCwgUW9icz1CYXNpbk9icyRRb2JzW0luZF9SdW5dKQ0KYGBgDQojIyMjIG5vdCBtdWNoIGRpZmZlcmVudC4gT2ggd2VsbCwgd2UgY2FuIGJlIGhhcHB5IHdpdGggZWl0aGVyIG9mIHRob3NlIG1ldHJpYyBzY29yZXMuIC0gcGF1c2UgZm9yIHRob3VnaHQgLSB3aGljaCBwYXJhbWV0ZXIgc2V0IHdvdWxkIHlvdSBjaG9vc2UgdG8gdXNlPyENCg0KIyMjIyBMZXQncyBkbyBzb21lIHZhbGlkYXRpb24NCmBgYHtyfQ0KIyBnbyBiYWNrIHRvIHRoZSBiZWdpbm5pbmcsIHJlZGVmaW5lIHRoZSBwZXJpb2QgdG8gcnVuIG9uICh0aGUgcGVyaW9kIHdlIGhhdmVuJ3QgdXNlZCBmb3IgY2FsaWJyYXRpb24sIG1pbnVzIHRoZSBmaXJzdCB5ZWFyIG5lZWRlZCBmb3Igd2FybSB1cCkNCkluZF9SdW4gPC0gc2VxKHdoaWNoKEJhc2luT2JzJERhdGVzUj09IjE5NjItMDEtMDEiKSwNCiAgICAgICAgICAgICB3aGljaChCYXNpbk9icyREYXRlc1I9PSIxOTgwLTEyLTMxIikpDQpSdW5PcHRpb25zIDwtIENyZWF0ZVJ1bk9wdGlvbnMoRlVOX01PRCA9IFJ1bk1vZGVsX0dSNEosDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5wdXRzTW9kZWwgPSBJbnB1dHNNb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbmRQZXJpb2RfUnVuID0gSW5kX1J1biwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbmRQZXJpb2RfV2FybVVwID0gTlVMTCkNCklucHV0c0NyaXQgPC0gQ3JlYXRlSW5wdXRzQ3JpdChGVU5fQ1JJVCA9IEVycm9yQ3JpdF9LR0UyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElucHV0c01vZGVsID0gSW5wdXRzTW9kZWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUnVuT3B0aW9ucyA9IFJ1bk9wdGlvbnMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUW9icyA9IEJhc2luT2JzJFFvYnNbSW5kX1J1bl0pDQpQYXJhbSA8LSBPdXRwdXRzQ2FsaWIkUGFyYW1GaW5hbFINCk91dHB1dHNNb2RlbCA8LSBSdW5Nb2RlbF9HUjRKKElucHV0c01vZGVsID0gSW5wdXRzTW9kZWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJ1bk9wdGlvbnMgPSBSdW5PcHRpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQYXJhbT0gUGFyYW0pDQpPdXRwdXRzQ3JpdCA8LSBFcnJvckNyaXRfS0dFMihJbnB1dHNDcml0ID0gSW5wdXRzQ3JpdCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPdXRwdXRzTW9kZWwgPSBPdXRwdXRzTW9kZWwpDQpgYGANCiMjIyMgc2xpZ2h0bHkgd29yc2UgdGhhbiB0aGUgY2FsaWJyYXRpb24gcGVyaW9kICgwLjk2MjEpIGJ1dCBub3QgYmFkIGF0IGFsbA0KDQojIyMjIGZpbmFsbHksIGxldHMgcnVuIHRoZSBtb2RlbCBmb3IgdGhlIHdob2xlIHRpbWUgcGVyaW9kIGFuZCBwbG90IGEgZHlncmFwaCBzbyB3ZSBjYW4gbG9vayBhdCB0aGUgdGltZXNlcmllcyBpbiBtb3JlIGRldGFpbA0KYGBge3J9DQpJbmRfUnVuIDwtIHNlcSh3aGljaChCYXNpbk9icyREYXRlc1I9PSIxOTYyLTAxLTAxIiksDQogICAgICAgICAgICAgd2hpY2goQmFzaW5PYnMkRGF0ZXNSPT0iMjAxNC0xMi0zMSIpKQ0KUnVuT3B0aW9ucyA8LSBDcmVhdGVSdW5PcHRpb25zKEZVTl9NT0QgPSBSdW5Nb2RlbF9HUjRKLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElucHV0c01vZGVsID0gSW5wdXRzTW9kZWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5kUGVyaW9kX1J1biA9IEluZF9SdW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW5kUGVyaW9kX1dhcm1VcCA9IE5VTEwpDQpQYXJhbSA8LSBPdXRwdXRzQ2FsaWIkUGFyYW1GaW5hbFINCk91dHB1dHNNb2RlbCA8LSBSdW5Nb2RlbF9HUjRKKElucHV0c01vZGVsID0gSW5wdXRzTW9kZWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJ1bk9wdGlvbnMgPSBSdW5PcHRpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQYXJhbT0gUGFyYW0pDQpwbG90X291dHB1dF9kYXRhIDwtIGFzLmRhdGEuZnJhbWUobWF0cml4KE5BLCBuY29sID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IGxlbmd0aChPdXRwdXRzTW9kZWwkRGF0ZXNSKSkpDQpjb2xuYW1lcyhwbG90X291dHB1dF9kYXRhKSA8LSBjKCJEYXRlIiwgIlFzaW0iLCAiUW9icyIpDQpwbG90X291dHB1dF9kYXRhJERhdGUgPC0gT3V0cHV0c01vZGVsJERhdGVzUg0KcGxvdF9vdXRwdXRfZGF0YSRRc2ltIDwtIE91dHB1dHNNb2RlbCRRc2ltDQpwbG90X291dHB1dF9kYXRhJFFvYnMgPC0gQmFzaW5PYnMkUW9ic1tJbmRfUnVuXQ0KcGxvdF9vdXRwdXRfZGF0YV94dHMgPC0gYXMueHRzKHBsb3Rfb3V0cHV0X2RhdGEsIG9yZGVyLmJ5ID0gcGxvdF9vdXRwdXRfZGF0YSREYXRlKQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpkeWdyYXBoKHBsb3Rfb3V0cHV0X2RhdGFfeHRzLCBtYWluPSJPYnNlcnZlZCBhbmQgU2ltdWxhdGVkIFJ1bm9mZiBmb3IgdGhlIFRoYW1lcyBhdCBLaW5nc3RvbiAoTmF0dXJhbGlzZWQpIiklPiUNCiAgZHlPcHRpb25zKGNvbG9ycyA9IFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCgzLCJTZXQxIilbMzoxXSklPiUNCiAgZHlBeGlzKCJ5IiwgbGFiZWw9IlJ1bm9mZiAobW0vZGF5KSIpJT4lDQogIGR5UmFuZ2VTZWxlY3RvcigpDQpgYGANCg==